package org.wrigut.scanlib;

import java.util.ArrayList;

/* Scanner.java
 * 
 * Project 1 - CS 3361
 * 
 * Authors: Chris Gutierrez
 *          Jordan Wright
 *          
 * This class will be used to scan an arbitrary amount of Strings, creating Tokens that will be used in parsing later.
 * These tokens will be stored in an ArrayList that will grow accordingly, for every string that is scanned.
 */

public class Scanner {
    private int state, col, tempcol, oldcol, linenum, p;
    private String source;
    private TransitionFunction table;
    private String buffer;
    //Only set if we reach the end of the string and we are in a multi-line comment
    private boolean error, debug;
    private ArrayList<Token> tokens;
    private KeywordTranslator translate;
    
    public Scanner(String source) {
    	this.source = source;
    	state = 1;
    	table = new TransitionFunction();
        tokens = new ArrayList<Token>();
        translate = new KeywordTranslator();
        error = false;
        //no char read yet
        p = 0;
        linenum = 1;
        tempcol = 1;
        oldcol = 1;
    }
    
    public Token scan() {
    	if (error) return null;
    	if (p < source.length()) {
    		if(!error) {
    			//setup the scanning loop
    	        int lastState = 1;
    	        char current;
    	        state = 1;
	            //Set the column value so that we can keep track of where we are
    	        col = p;
	            buffer = "";
	            do {
		            //If valid input (not an unknown character that would cause an error)
		            if (p < source.length()) {
		                lastState = state;
		                //Get the next character
		                current = readChar();
		                buffer += current;
		                //Grab that next state
		                state = table.nextState(state, current);
		                //Check to see if the next char is valid, and we haven't reached the end of the string
		                while(state != 0 && p < source.length()) {
		                    lastState = state;
		                    //Get the next character
		                    current = readChar();
		                    buffer += current;
		                    state = table.nextState(state, current);
		                }
		                //Check to see if we got a 0 state or reached end of string
		                if(state == 0) {
		                    //Roll back i so we can grab the next char - Roll back 2 because of i++ in for loop and previous i ++
		                    unreadChar();
		                    buffer = buffer.substring(0, buffer.length()-1);
		                    //if we can make a token...
		                    if (table.isFinal(lastState)) {
		                    	Token t = new Token(table.getTokenType(lastState),linenum,col-tempcol+1,buffer);
		                    	translate.translate(t);
		                    	return t;
		                    } else if (lastState == 17 || lastState == 18) {
		                    	//just got out of a comment, whites_space
		                    	//try again
		                    	state = 1;
		                    	buffer = "";
		                    	col = p;
		                    	continue;
		                    } else {
		                    	//ERROR
		                    	error = true;
		                    	return null;
		                    }
		                }
		                //End of source
		                //If we reached the end of the string, then we need to check to see if we're in a final state
		                else {
		                    if (table.isFinal(state)) {
		                    	Token t = new Token(table.getTokenType(state),linenum,col-tempcol+1,buffer);
		                    	translate.translate(t);
		                    	return t;
		                    } else if (state == 17 || state == 18 || state == 3) {
		                    	return new Token(TokenType.EOF,linenum,col-tempcol+1,null);
		                    } else {
		                    	//ERROR
		                    	error = true;
		                    	return null;
		                    }
		                }
		            }
		           	//Else, if we had invalid input, we should output an error and break out of this loop
		            else {
		                System.out.println("Error at line: "+this.linenum+" column: "+(p-tempcol+1));
		                error = true;
		                return null;
		            }
	            }
	            //to ignore comments+white_space
	            while (state != 17 || state != 18);
    	    }
    	}
    	return new Token(TokenType.EOF,linenum,col-tempcol+1,null);
    }
    
    public ArrayList<Token> scanAll() {
    	ArrayList<Token> tokens = new ArrayList<Token>();
    	Token lastToken = scan();
    	while (lastToken != null && lastToken.getType() != TokenType.EOF) {
    		//valid token
    		tokens.add(lastToken);
    		lastToken = scan();
    	}
    	//error
    	if (lastToken == null) return null;
    	//Completed
    	tokens.add(new Token(TokenType.EOF,0,0,null));
    	return tokens;
    }
    
    //This function reverts the scanner to its initial state, intended to use
    //with scanAll() to get all the tokens even if some have been already read
    public void reset() {
    	p = 0;
    	state = 1;
    	linenum = 1;
    	col = 1;
    	tempcol = 1;
    	error = false;
    }
    
    //---------- Private Methods
    
    //Get next char, advance p
    private char readChar() {
    	char c = source.charAt(p);
    	p++;
    	if (c == '\n') {
    		linenum++;
    		//previous line
    		oldcol = tempcol;
    		tempcol = p;
    		debugOutput("Increasing linenum");
    	}
    	return c;
    }
    
    //Get the last char, decrement p
    private char unreadChar() {
    	char c = 0;
    	p--;
    	if (p < source.length()) {
    		c = source.charAt(p);
    	}
    	if (c == '\n') {
    		linenum--;
    		tempcol = oldcol;
    		debugOutput("Decreasing linenum");
    	}
    	return c;
    }
    
    //---------- Public Methods (Accessors and ..the other ones (it's 3:45 AM..))
    
    //-------- Get methods (mainly for debugging purposes) ------------------------------------
    public TransitionFunction getTable()
    {
        return this.table;
    }
    
    public String getBuffer()
    {
        return this.buffer;
    }
    
    
    //------- Set methods
    public void setSource(String s)
    {
    	reset();
        source = s;
    }
    
    /* ---------- This function will return the final ArrayList of tokens ----------
     * This will be helpful because it will allow us to pass strings one at a time
     * to the scanner, which will keep an updated list of all tokens, as opposed to 
     * having to pass one large string. 
     * Will be implemented with the Token class
     * -----------------------------------------------------------------------------*/
     
    public ArrayList<Token> getTokens()
    {
        return this.tokens;
    }
    
    public void setDebug(boolean debug) {
    	this.debug = debug;
    }
    
    private void debugOutput(String msg) {
    	if (debug) System.out.println(msg);
    }
}
